<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="fj9Ly" id="fj9Ly"><span data-lake-id="uac02914b" id="uac02914b">典型回答</span></h1>
  <p data-lake-id="u2996519d" id="u2996519d" style="text-align: justify"><span data-lake-id="u79d604c0" id="u79d604c0" style="color: rgb(85, 85, 85)">为了保护操作系统的安全，通过缓存加快系统读写，会将内存分为</span><strong><span data-lake-id="u2c8fd8f9" id="u2c8fd8f9" style="color: rgb(85, 85, 85)">用户空间和内核空间</span></strong><span data-lake-id="ua07b7b43" id="ua07b7b43" style="color: rgb(85, 85, 85)">两个部分。</span><strong><span data-lake-id="uc1593a41" id="uc1593a41" style="color: rgb(85, 85, 85)">如果用户想要操作内核空间的数据，则需要把数据从内核空间拷贝到用户空间（数据会放到内核空间的page cache中，这种也叫缓存IO）</span></strong><span data-lake-id="udbd0ee74" id="udbd0ee74" style="color: rgb(85, 85, 85)">。</span></p>
  <p data-lake-id="u673e62da" id="u673e62da" style="text-align: justify"><span data-lake-id="u4d979873" id="u4d979873" style="color: rgb(85, 85, 85)">举个栗子，如果服务器收到了从客户端过来的请求，并且想要进行处理，那么需要经过这几个步骤：</span></p>
  <ul list="u3a1af322">
   <li fid="u4c706de0" data-lake-id="u03444106" id="u03444106" style="text-align: justify"><span data-lake-id="u9f01a9c7" id="u9f01a9c7" style="color: rgb(85, 85, 85)">服务器的网络驱动接受到消息之后，向内核申请空间，并在收到完整的数据包（这个过程会产生延时，因为有可能是通过分组传送过来的）后，将其复制到内核空间；</span></li>
   <li fid="u4c706de0" data-lake-id="u0e9b77a8" id="u0e9b77a8" style="text-align: justify"><span data-lake-id="u72b44941" id="u72b44941" style="color: rgb(85, 85, 85)">数据从内核空间拷贝到用户空间；</span></li>
   <li fid="u4c706de0" data-lake-id="u5b0b2d32" id="u5b0b2d32" style="text-align: justify"><span data-lake-id="ud8042664" id="ud8042664" style="color: rgb(85, 85, 85)">用户程序进行处理。</span></li>
  </ul>
  <p data-lake-id="ub15d2f1f" id="ub15d2f1f" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/719664/1675480879556-6db57431-13f1-4573-b370-be780380244f.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_18%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u672e790f" id="u672e790f" style="color: rgb(85, 85, 85)">￼</span></p>
  <p data-lake-id="u07bcafea" id="u07bcafea" style="text-align: justify"><span data-lake-id="u2c5b2a5a" id="u2c5b2a5a" style="color: rgb(85, 85, 85)">我们再详细的探究服务器中的文件读取，</span><span data-lake-id="u4b101528" id="u4b101528" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">对于Linux来说，Linux是一个将所有的外部设备都看作是文件来操作的操作系统，在它看来：</span><strong><span data-lake-id="u0e5b296a" id="u0e5b296a" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">everything is a file</span></strong><span data-lake-id="u7d192ff8" id="u7d192ff8" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">，那么我们就把对与外部设备的操作都看作是对文件进行操作。而且我们对一个文件进行读写，都需要通过调用内核提供的系统调用。</span></p>
  <p data-lake-id="u5bb3b79a" id="u5bb3b79a" style="text-align: justify"><span data-lake-id="ua60fda67" id="ua60fda67" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">而在Linux中，一个基本的IO会涉及到两个系统对象：一个是调用这个IO的进程对象（用户进程），另一个是系统内核。也就是说，当一个read操作发生时，将会经历这些阶段：</span></p>
  <ul list="u209e28ad">
   <li fid="ub504ed81" data-lake-id="u6c693bc4" id="u6c693bc4" style="text-align: justify"><span data-lake-id="uc831e30b" id="uc831e30b" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">通过read系统调用，向内核发送读请求；</span></li>
   <li fid="ub504ed81" data-lake-id="u5f10db76" id="u5f10db76" style="text-align: justify"><span data-lake-id="u88b35545" id="u88b35545" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">内核向硬件发送读指令，并等待读就绪；</span></li>
   <li fid="ub504ed81" data-lake-id="u5bce810c" id="u5bce810c" style="text-align: justify"><span data-lake-id="ub126722b" id="ub126722b" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">DMA把将要读取的数据复制到指定的内核缓存区中；</span></li>
   <li fid="ub504ed81" data-lake-id="u29b333f6" id="u29b333f6" style="text-align: justify"><strong><span data-lake-id="ucb802662" id="ucb802662" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">内核将数据从内核缓存区拷贝到用户进程空间中</span></strong><span data-lake-id="u26b3a232" id="u26b3a232" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">。</span></li>
  </ul>
  <p data-lake-id="ue588ef30" id="ue588ef30" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/719664/1675481188513-495aa3d0-f1c4-4537-8d79-b8642d72a343.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_20%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u1c3bd6e3" id="u1c3bd6e3"><br><br></span></p>
  <p data-lake-id="u639bd7f8" id="u639bd7f8" style="text-align: justify"><span data-lake-id="ub3f609ec" id="ub3f609ec" style="color: rgb(85, 85, 85)">正是由于上面的几个阶段，导致了file中的数据被用户进程消费是需要过程的，这也就延伸出了五种IO方式，分别是同步阻塞型IO模型、同步非阻塞型IO模型、IO复用模型、信号驱动模型以及异步IO模型</span></p>
  <p data-lake-id="uda7199f6" id="uda7199f6" style="text-align: justify"><span data-lake-id="u7b75da31" id="u7b75da31" style="color: rgb(85, 85, 85)">​</span><br></p>
  <h1 data-lake-id="NyLYa" id="NyLYa"><span data-lake-id="u7f593fc2" id="u7f593fc2" style="color: rgb(85, 85, 85)">扩展知识</span></h1>
  <p data-lake-id="uca2f9b79" id="uca2f9b79" style="text-align: justify"><span data-lake-id="u864243b2" id="u864243b2" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="ue5a027e5" id="ue5a027e5" style="text-align: justify"><span data-lake-id="u38de4e34" id="u38de4e34" style="color: rgb(85, 85, 85)">我们通过小J</span><span data-lake-id="u04fa1117" id="u04fa1117" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">要去银行柜台办事，拿号排队的例子来分别说一下这五种IO模型。</span></p>
  <h2 data-lake-id="x06ga" id="x06ga"><span data-lake-id="u3bb4775a" id="u3bb4775a" style="color: rgb(85, 85, 85)">同步阻塞IO模型</span></h2>
  <p data-lake-id="ub85b2c70" id="ub85b2c70" style="text-align: justify"><strong><span data-lake-id="uf6471f8b" id="uf6471f8b" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">从系统调用recv到将数据从内核复制到用户空间并返回，在这段时间内进程始终阻塞</span></strong><span data-lake-id="uf7007aa0" id="uf7007aa0" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">。就相当于，小J想去柜台办理业务，如果柜台业务繁忙，他也要排队，直到排到他办理完业务，才能去做别的事。显然，这个IO模型是同步且阻塞的。</span></p>
  <p data-lake-id="u783744ac" id="u783744ac" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/719664/1675481315872-7780dc25-9471-4583-a10a-138420bcb8cf.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_17%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u1b8023cb" id="u1b8023cb" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">￼</span></p>
  <h2 data-lake-id="UDjzk" id="UDjzk"><span data-lake-id="ufd1dbe15" id="ufd1dbe15" style="color: rgb(85, 85, 85)">同步非阻塞IO模型</span></h2>
  <p data-lake-id="ubaa1d39f" id="ubaa1d39f" style="text-align: justify"><strong><span data-lake-id="u259d8f26" id="u259d8f26" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">在这里recv不管有没有获得到数据都返回，如果没有数据的话就过段时间再调用recv看看，如此循环</span></strong><span data-lake-id="u037492ad" id="u037492ad" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">。就像是小J来柜台办理业务，发现柜员休息，他离开了，过一会又过来看看营业了没，直到终于碰到柜员营业了，这才办理了业务。而小J在中间离开的时间，可以做他自己的事情。但是这个模型只有在检查无数据的时候是非阻塞的，在数据到达的时候依然要等待复制数据到用户空间（办理业务），因此它还是同步IO。</span></p>
  <p data-lake-id="u1d4c80ba" id="u1d4c80ba" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/719664/1675481315872-adeeb75e-34c8-4301-9f90-31ae243caa41.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_17%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="ub358b1eb" id="ub358b1eb" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">￼</span></p>
  <h2 data-lake-id="otkqt" id="otkqt"><span data-lake-id="ua25c0f6b" id="ua25c0f6b" style="color: rgb(85, 85, 85)">IO复用模型</span></h2>
  <p data-lake-id="u2a555eb6" id="u2a555eb6" style="text-align: justify"><strong><span data-lake-id="ub1a05d9b" id="ub1a05d9b" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">在IO复用模型中，调用recv之前会先调用select或poll，这两个系统调用都可以在内核准备好数据（网络数据已经到达内核了）时告知用户进程，它准备好了，这时候再调用recv时是一定有数据的。因此在这一模型中，进程阻塞于select或poll，而没有阻塞在recv上</span></strong><span data-lake-id="ue6d00f3c" id="ue6d00f3c" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">。就相当于，小J来银行办理业务，大堂经理告诉他现在所有柜台都有人在办理业务，等有空位再告诉他。于是小J就等啊等（select或poll调用中），过了一会儿大堂经理告诉他有柜台空出来可以办理业务了，但是具体是几号柜台，你自己找下吧，于是小J就只能挨个柜台地找。</span></p>
  <p data-lake-id="u6e23792e" id="u6e23792e" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/719664/1675481315873-92a3540b-306d-4f63-b05b-85dfd0c1c8f1.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_18%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="ud6b525c4" id="ud6b525c4" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">￼</span></p>
  <h2 data-lake-id="fO64l" id="fO64l"><span data-lake-id="u59fe2cf9" id="u59fe2cf9" style="color: rgb(85, 85, 85)">信号驱动IO模型</span></h2>
  <p data-lake-id="u02f9f856" id="u02f9f856" style="text-align: justify"><strong><span data-lake-id="u5c0df744" id="u5c0df744" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">此处会通过调用sigaction注册信号函数，在内核数据准备好的时候系统就中断当前程序，执行信号函数（在这里调用recv）</span></strong><span data-lake-id="ue7eb5ac0" id="ue7eb5ac0" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">。相当于，小J让大堂经理在柜台有空位的时候通知他（注册信号函数），等没多久大堂经理通知他，因为他是银行的VIPPP会员，所以专门给他开了一个柜台来办理业务，小J就去特席柜台办理业务了。但即使在等待的过程中是非阻塞的，但在办理业务的过程中依然是同步的。</span></p>
  <p data-lake-id="u4c5010a3" id="u4c5010a3" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/719664/1675481315866-e20ab78a-9f09-4212-8538-0dfb28ec3872.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_18%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="ua2a111b6" id="ua2a111b6" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">￼</span></p>
  <h2 data-lake-id="nzPc9" id="nzPc9"><span data-lake-id="ud828090d" id="ud828090d" style="color: rgb(85, 85, 85)">异步IO模型</span></h2>
  <p data-lake-id="u640da97e" id="u640da97e" style="text-align: justify"><strong><span data-lake-id="uc2da26a5" id="uc2da26a5" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">调用aio_read令内核把数据准备好，并且复制到用户进程空间后执行事先指定好的函数</span></strong><span data-lake-id="u97e950f8" id="u97e950f8" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">。就像是，小J交代大堂经理把业务给办理好了就通知他来验收，在这个过程中小J可以去做自己的事情。这就是真正的异步IO。</span></p>
  <p data-lake-id="u5dac180c" id="u5dac180c" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/719664/1675481315869-16f5977d-ffe1-49ae-b1d7-ad6a549cf19b.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_18%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u2b1e9a69" id="u2b1e9a69" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">￼</span></p>
  <p data-lake-id="u67c585a3" id="u67c585a3" style="text-align: justify"><span data-lake-id="u37a02594" id="u37a02594" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">我们可以看到，前四种模型都是属于同步IO，因为在内核数据复制到用户空间的这一过程都是阻塞的。而最后一种异步IO，通过将IO操作交给操作系统处理，当前进程不关心具体IO的实现，后来再通过回调函数，或信号量通知当前进程直接对IO返回结果进行处理。</span></p>
  <h1 data-lake-id="rP29Z" id="rP29Z"><span data-lake-id="uc0a8fff2" id="uc0a8fff2" style="color: rgb(85, 85, 85)">知识扩展</span></h1>
  <h2 data-lake-id="nP0VK" id="nP0VK"><span data-lake-id="uab5f384f" id="uab5f384f">什么是同步，异步，阻塞，非阻塞？</span></h2>
  <h2 data-lake-id="csAcv" id="csAcv"><span data-lake-id="u75f98ea6" id="u75f98ea6">如何理解select，poll，epoll？</span></h2>
 </body>
</html>